All files / src/services appVersion.ts

0% Statements 0/37
0% Branches 0/19
0% Functions 0/9
0% Lines 0/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125                                                                                                                                                                                                                                                         
import axios from 'axios';
import apiService from './api';
import { API_CONFIG } from '@/constants';
import { ApiResult, ApiError } from '@/types';
import { STORAGE_KEYS } from '@/constants';
 
export interface AppVersion {
    id: number;
    app_type: 'phone' | 'tv';
    version: string;
    version_code: number;
    apk_filename: string;
    file_size: number;
    file_hash: string;
    force_update: boolean;
    release_notes?: string;
    min_required_version?: string;
    is_active: boolean;
    created_at: string;
    updated_at: string;
    download_url: string;
}
 
export interface CreateAppVersionRequest {
    app_type: 'phone' | 'tv';
    version: string;
    version_code: number;
    force_update: boolean;
    release_notes?: string;
    min_required_version?: string;
    apk_file: File;
}
 
export interface UpdateAppVersionRequest {
    force_update?: boolean;
    release_notes?: string;
    min_required_version?: string;
    is_active?: boolean;
}
 
// Get the direct backend URL - uses API_CONFIG.BASE_URL which properly handles env vars
const getDirectBackendUrl = (): string => {
    return API_CONFIG.BASE_URL;
};
 
// Get auth token from storage
const getAuthToken = (): string | null => {
    if (typeof window === 'undefined') return null;
    return localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN) || null;
};
 
export const appVersionService = {
    // Public (for testing mainly, usually accessed by apps)
    checkVersion: async (appType: 'phone' | 'tv', currentCode?: number) => {
        return apiService.get(`/api/app/version`, {
            params: { app_type: appType, current_version_code: currentCode }
        });
    },
 
    // Admin
    listVersions: async (appType?: 'phone' | 'tv'): Promise<ApiResult<AppVersion[]>> => {
        return apiService.get<AppVersion[]>('/api/admin/app-versions', {
            params: { app_type: appType }
        });
    },
 
    createVersion: async (data: CreateAppVersionRequest, onUploadProgress?: (progress: number) => void): Promise<ApiResult<AppVersion>> => {
        const formData = new FormData();
        formData.append('app_type', data.app_type);
        formData.append('version', data.version);
        formData.append('version_code', data.version_code.toString());
        formData.append('force_update', data.force_update.toString());
        if (data.release_notes) formData.append('release_notes', data.release_notes);
        if (data.min_required_version) formData.append('min_required_version', data.min_required_version);
        formData.append('apk_file', data.apk_file);
 
        // CRITICAL: Upload directly to backend, bypassing Next.js proxy
        // The Next.js rewrite proxy has an internal 30s timeout that cannot be configured
        const directUrl = getDirectBackendUrl();
        const token = getAuthToken();
 
        console.log('📦 Uploading APK directly to backend:', directUrl);
 
        try {
            const response = await axios.post<AppVersion>(
                `${directUrl}/api/admin/app-versions`,
                formData,
                {
                    headers: {
                        'Authorization': token ? `Bearer ${token}` : '',
                        'X-Admin-Panel': 'true'},
                    onUploadProgress: (progressEvent) => {
                        if (onUploadProgress && progressEvent.total) {
                            const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
                            onUploadProgress(progress);
                        }
                    },
                    timeout: 600000, // 10 minutes
                    maxContentLength: Infinity,
                    maxBodyLength: Infinity}
            );
            return { success: true, data: response.data };
        } catch (error: any) {
            const apiError: ApiError = {
                error: error.response?.data?.error || 'Upload failed',
                details: error.response?.data?.details || error.message || 'Unknown error',
                timestamp: new Date().toISOString()};
            return { success: false, error: apiError };
        }
    },
 
    updateVersion: async (id: number, data: UpdateAppVersionRequest): Promise<ApiResult<AppVersion>> => {
        return apiService.put<AppVersion>(`/api/admin/app-versions/${id}`, data);
    },
 
    deleteVersion: async (id: number): Promise<ApiResult<void>> => {
        return apiService.delete<void>(`/api/admin/app-versions/${id}`);
    },
 
    getDownloadUrl: (filename: string) => {
        return `${API_CONFIG.BASE_URL}/api/app/download/${filename}`;
    }
};